package info.openrocket.core.file.simplesax;

import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import info.openrocket.core.logging.WarningSet;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

/**
 * A "simple SAX" XML reader. This system imposes the limit that an XML element
 * may
 * contain either textual (non-whitespace) content OR additional elements, but
 * not
 * both. This holds true for both the OpenRocket and RockSim design formats and
 * the
 * RockSim engine definition format.
 * <p>
 * The actual handling is performed by subclasses of {@link ElementHandler}. The
 * initial handler is provided to the
 * {@link #readXML(InputSource, ElementHandler, WarningSet)}
 * method.
 *
 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
 */
public class SimpleSAX {
	static final XMLReaderCache cache = new XMLReaderCache(10);

	/**
	 * Read a simple XML file.
	 *
	 * @param source         the SAX input source.
	 * @param initialHandler the initial content handler.
	 * @param warnings       a warning set to store warning (cannot be
	 *                       <code>null</code>).
	 * @throws IOException  if an I/O exception occurs while reading.
	 * @throws SAXException if e.g. malformed XML is encountered.
	 */
	public static void readXML(InputSource source, ElementHandler initialHandler, WarningSet warnings)
			throws IOException, SAXException {

		DelegatorHandler xmlhandler = new DelegatorHandler(initialHandler, warnings);

		XMLReader reader = cache.createXMLReader();
		reader.setContentHandler(xmlhandler);
		reader.setErrorHandler(xmlhandler);
		try {
			reader.parse(source);
		} finally {
			reader.setContentHandler(null);
			reader.setErrorHandler(null);
			cache.releaseXMLReader(reader);
		}
	}

	private static class XMLReaderCache {
		private final SAXParserFactory parserFactory;

		private final BlockingQueue<XMLReader> queue;

		private XMLReaderCache(int maxSize) {
			parserFactory = SAXParserFactory.newInstance();
			parserFactory.setNamespaceAware(true);
			queue = new LinkedBlockingQueue<>(maxSize);
		}

		private XMLReader createXMLReader() throws SAXException {
			XMLReader reader = queue.poll();
			if (reader == null) {
				try {
					reader = XMLReaderFactory.createXMLReader();
					SAXParser parser = parserFactory.newSAXParser();
					reader = parser.getXMLReader();
				} catch (ParserConfigurationException ignore) {
					System.out.print(ignore);
				}
			}
			return reader;
		}

		private void releaseXMLReader(XMLReader reader) {
			// force references to null to encourage garbage collection.
			reader.setContentHandler(null);
			reader.setErrorHandler(null);
			queue.offer(reader);
		}
	}
}
